package com.foreveross.bsl.common.utils.balance.cxf;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.service.model.EndpointInfo;
import org.apache.cxf.service.model.ServiceInfo;

import javax.xml.namespace.QName;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * LoadBalance strategy based on a static cluster represented by
 * multiple endpoints associated with the same service instance.
 *
 * @author Vector Ho  Date:2014-10-17 23:26
 * @warning most code from AbstractStaticFailoverStrategy!!!
 * @see org.apache.cxf.clustering.AbstractStaticFailoverStrategy
 */
public abstract class AbstractLoadBalanceStrategy implements LoadBalanceStrategy {
	
    private static final Log logger = LogFactory.getLog(AbstractLoadBalanceStrategy.class);

    private List<String> alternateAddresses;

    // for global, not for current invocation
    private boolean removeFailedEndpoint = true; // 如果设置为true, 但没有监测服务器状态的程序, 新增的或者复活的服务器则无法被恢复到地址列表中.
    private boolean alwaysChangeEndpoint = true;


    public void setAlternateAddresses(List<String> alternateAddresses) {
        this.alternateAddresses = alternateAddresses;
    }

    /**
     * Get the alternate addresses for this invocation.
     *
     * @param exchange the current Exchange
     * @return a List of alternate addresses if available
     */
    public List<String> getAlternateAddresses(Exchange exchange) {
        return alternateAddresses != null ? new ArrayList<String>(alternateAddresses) : null;
    }

    /**
     * Select one of the alternate addresses for a retried invocation.
     *
     * @param alternates List of alternate addresses if available
     * @return the selected address
     */
    public String selectAlternateAddress(List<String> alternates) {
        String selected = null;
        if (alternates != null && alternates.size() > 0) {
            selected = getNextAlternate(alternates);
            logger.info("switch to addresss: " + selected);
        } else {
            logger.warn("not find avaiable alternate targets");
        }
        return selected;
    }

    /**
     * Get the alternate endpoints for this invocation.
     *
     * @param exchange the current Exchange
     * @return a List of alternate endpoints if available
     */
    public List<Endpoint> getAlternateEndpoints(Exchange exchange) {
        return getEndpoints(exchange, false);
    }

    /**
     * Select one of the alternate endpoints for a retried invocation.
     *
     * @param alternates List of alternate endpoints if available
     * @return the selected endpoint
     */
    public Endpoint selectAlternateEndpoint(List<Endpoint> alternates) {
        Endpoint selected = null;
        if (alternates != null && alternates.size() > 0) {
            selected = getNextAlternate(alternates);
            logger.warn("switch to alternate endpoint: " + selected.getEndpointInfo().getName()
                    + " " + selected.getEndpointInfo().getAddress());
        } else {
            logger.warn("not find avaiable alternate targets");
        }
        return selected;
    }

    /**
     * Get the endpoints for this invocation.
     *
     * @param exchange the current Exchange
     * @param acceptCandidatesWithSameAddress
     *                 true to accept candidates with the same address
     * @return a List of alternate endpoints if available
     */
    protected List<Endpoint> getEndpoints(Exchange exchange, boolean acceptCandidatesWithSameAddress) {
        Endpoint endpoint = exchange.get(Endpoint.class);
        Collection<ServiceInfo> services = endpoint.getService().getServiceInfos();
        QName currentBinding = endpoint.getBinding().getBindingInfo().getName();
        List<Endpoint> alternates = new ArrayList<Endpoint>();
        for (ServiceInfo service : services) {
            Collection<EndpointInfo> candidates = service.getEndpoints();
            for (EndpointInfo candidate : candidates) {
                QName candidateBinding = candidate.getBinding().getName();
                if (candidateBinding.equals(currentBinding)) {
                    if (acceptCandidatesWithSameAddress || !candidate.getAddress().equals(
                            endpoint.getEndpointInfo().getAddress())) {
                        Endpoint alternate =
                                endpoint.getService().getEndpoints().get(candidate.getName());
                        if (alternate != null) {
                            logger.info("load balance candidate accepted: " + candidate.getName());
                            alternates.add(alternate);
                        }
                    }
                } else {
                    logger.info("load balance candidate rejected: " + candidate.getName() + " " + candidateBinding);
                }
            }
        }
        return alternates;
    }

    /**
     * Get next alternate endpoint.
     *
     * @param alternates non-empty List of alternate endpoints
     * @return next address or endpoint
     */
    protected abstract <T> T getNextAlternate(List<T> alternates);

    @Override
    public void removeAlternateAddress(String address) {
        alternateAddresses.remove(address);
    }

    @Override
    public boolean isRemoveFailedEndpoint() {
        return removeFailedEndpoint;
    }

    public void setRemoveFailedEndpoint(boolean remove) {
        this.removeFailedEndpoint = remove;
    }

    @Override
    public boolean isAlwaysChangeEndpoint() {
        return alwaysChangeEndpoint;
    }

    public void setAlwaysChangeEndpoint(boolean alwaysChangeEndpoint) {
        this.alwaysChangeEndpoint = alwaysChangeEndpoint;
    }
}
